/**
 *  @file: UserKeyTask.cpp
 *  @author: nwz
 *  @brief: 
 *  @date: 2024��8��1��
 * 
 */

#define TAG "userkey"
#include <code/task/UserKeyTask.h>
#define KEY_DEBOUNCE_TIME	20
namespace task {

typedef struct {
	const UserKeyTask::Key id;
	const GPIO_TypeDef *port;
	const uint32_t pin;
	const IRQn_Type irq;
	uint32_t start_time;
	uint32_t timer_count;
	bool down;
	bool stop_timer;
} key_t;

static QueueHandle_t que;
static key_t keys[] = {
//
		{ UserKeyTask::Key::ON_OFF, GPIOA, GPIO_PIN_2, EXTI2_IRQn },
		// k1 = B5, k2=D4, k3=D3
		//
		{ UserKeyTask::Key::K1, GPIOB, GPIO_PIN_5, EXTI5_IRQn },
		//
		{ UserKeyTask::Key::K2, GPIOD, GPIO_PIN_4, EXTI4_IRQn },
		//
		{ UserKeyTask::Key::K3, GPIOD, GPIO_PIN_3, EXTI3_IRQn },
//
		};

extern "C" {
static void timer_callback(TimerHandle_t xTimer);

static void inline interrupt_disable_irq(int id) {
	HAL_NVIC_DisableIRQ(keys[id].irq);
}
static void send_key_event(UserKeyTask::Key id, bool down) {
	UserKeyTask::notify_event_t evt;
	MEM_ZERO(evt);
	evt.id = (UserKeyTask::Key) id;
	if (down)
		evt.event = UserKeyTask::Event::DOWN;
	else
		evt.event = UserKeyTask::Event::UP;
	ASSERT(xQueueSend(que,&evt,DEFAULT_WAIT_TICKS) == pdTRUE);
}

static void interrupt_start_timer(int id) {
	auto timer = xTimerCreate("timer", pdMS_TO_TICKS(KEY_DEBOUNCE_TIME), pdTRUE,
			&keys[id], timer_callback);
	ASSERT(timer);
	BaseType_t pxHigherPriorityTaskWoken = pdFALSE;
	ASSERT(xTimerStartFromISR(timer,&pxHigherPriorityTaskWoken) == pdPASS);
}

static void timer_callback(TimerHandle_t timer) {
	key_t *key = (key_t*) pvTimerGetTimerID(timer);
	key->timer_count++;
	bool stop = false;
	auto key_value = HAL_GPIO_ReadPin(key->port, key->pin);
	if (key->timer_count == 1) {
		if (key_value == GPIO_PIN_RESET) { ///< key down
			send_key_event(key->id, true);
		} else {
			stop = true;
		}
	} else {
		if (key->stop_timer) {
			if (key_value == GPIO_PIN_SET) { // key up
				send_key_event(key->id, false);
				stop = true;
			} else {
				key->stop_timer = false;
			}
		} else if (key_value == GPIO_PIN_SET) { // key up
			key->stop_timer = true;
		}
	}

	if (stop) {
		key->stop_timer = false;
		key->timer_count = 0;
		ASSERT(xTimerStop(timer,DEFAULT_WAIT_TICKS) == pdTRUE);
		ASSERT(xTimerDelete(timer,DEFAULT_WAIT_TICKS) == pdTRUE);
		HAL_NVIC_EnableIRQ(key->irq);
	}
}

static void gpio_irq_process(UserKeyTask::Key key) {
	if (!que)
		return;

	int id = (int) key;
	interrupt_disable_irq(id);
	interrupt_start_timer(id);
}

void gpio_pa2_interrupt() {
	gpio_irq_process(UserKeyTask::Key::ON_OFF);
}

void gpio_pb5_interrupt() {
	gpio_irq_process(UserKeyTask::Key::K1);
}
void gpio_pd4_interrupt() {
	gpio_irq_process(UserKeyTask::Key::K2);
}
void gpio_pd3_interrupt() {
	gpio_irq_process(UserKeyTask::Key::K3);
}
} // extend C

UserKeyTask::UserKeyTask(LcdTask &lcdTask) :
		lcdTask { lcdTask } {
// TODO Auto-generated constructor stub
	for (int i = 0; i < ARRAY_SIZE(keys); i++) {
		ASSERT(i == (int )keys[i].id);
	}
}

UserKeyTask::~UserKeyTask() {
// TODO Auto-generated destructor stub
}

int UserKeyTask::init() {
	que = xQueueCreate(ARRAY_SIZE(keys)*2, sizeof(notify_event_t));
	ASSERT(que);

	queue = xQueueCreate(ARRAY_SIZE(keys)*4, sizeof(notify_event_t));
	ASSERT(queue);
	return 0;
}

uint32_t UserKeyTask::calculateSleepTick() {
	auto time = portMAX_DELAY;
	for (int i = 0; i < ARRAY_SIZE(keys); i++) {
		if (keys[i].down) {
			time = pdMS_TO_TICKS(100);
			break;
		}
	}
	return time;
}

void UserKeyTask::run_() {
	notify_event_t notify;
	while (true) {
		auto sleep_ticks = calculateSleepTick();
		if (xQueueReceive(que, &notify, sleep_ticks) == pdTRUE) {
			int id = (int) notify.id;
			if (notify.event == Event::UP) { // up event
				if (keys[id].down) {
					auto dur = NOW() - keys[id].start_time;
					if (dur >= 100 && dur <= 1000) {
						// click event
//						LOG_D("key %d click event dur %d" NL, (int )id,dur);
						notify.event = Event::CLICK;
						xQueueSend(queue, &notify, DEFAULT_WAIT_TICKS);
						MainTask::nofityKeyEvent();
					}
					keys[id].down = false;
					keys[id].start_time = 0;
				}
			} else { // down event
				if (!lcdTask.isShutdown()) {
					keys[id].down = true;
					keys[id].start_time = NOW();
				}
			}
		}

		// timeout
		for (int i = 0; i < ARRAY_SIZE(keys); i++) {
			if (keys[i].down) {
				auto dur = NOW() - keys[i].start_time;
				if (dur >= KEY_LONG_CLICK_TIME) {
					keys[i].down = false;
					keys[i].start_time = 0;

//					LOG_D("key %d long click" NL, i);
					notify.id = (Key) i;
					notify.event = Event::LONG_CLICK;
					xQueueSend(queue, &notify, DEFAULT_WAIT_TICKS);
					MainTask::nofityKeyEvent();
				}
			}
		}

		MainTask::notifyUserInputTime();
	}
}

} /* namespace task */
